﻿using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using Swimj.Core.Extensions;
using Swimj.Core.Utilities;

namespace Swimj.Core
{
    public static class Check
    {
        private static readonly string argumentNullExceptionMessage = "Parameter '{0}' in Method '{1}' is null" +
                                                                      Environment.NewLine +
                                                                      "Method '{1}' was called from '{2}'";

        public static T ThrowIfNull<T>(this T input, string paramName)
        {
            return input.ThrowIfNull<ArgumentNullException, T>(paramName);
        }

        public static T ThrowIfNull<TException, T>(this T input, string message) where TException : Exception, new()
        {
            if (input.NotNull())
            {
                return input;
            }

            var exception = typeof(TException).CreateInstance<TException>(message);
            throw exception;
        }

        public static T ThrowIfNull<T>([NotNull] T? argument, bool throwOnEmptyString = false,
            [CallerArgumentExpression("argument")] string? paramName = null)
            where T : class
        {
#if NET6_0_OR_GREATER
            ArgumentNullException.ThrowIfNull(argument, paramName);
            if (throwOnEmptyString && argument is string s && string.IsNullOrEmpty(s))
                throw new ArgumentNullException(paramName);
#else
        if (argument is null || throwOnEmptyString && argument is string s && string.IsNullOrEmpty(s))
            throw new ArgumentNullException(paramName);
#endif
            return argument;
        }

        public static void Requires(bool condition, Func<Exception> exceptionCreateFactory)
        {
            if (!condition)
                throw exceptionCreateFactory();
        }

        public static void Requires<TException>(bool condition)
            where TException : Exception, new()
        {
            if (!condition)
                throw new TException();
        }

        public static void Requires<TException>(bool condition, string message)
            where TException : Exception
        {
            if (!condition)
                throw (TException)Activator.CreateInstance(typeof(TException), message);
        }

        public static void NotNull(Expression<Func<object>> expression1, Expression<Func<object>> expression2,
            params Expression<Func<object>>[] parameters)
        {
            foreach (var parameterName in from expression in parameters.Concat(new[] { expression1, expression2 })
                     let parameterToCheck = expression.Compile()()
                     where parameterToCheck == null
                     select expression.GetMemberName())
                throw new ArgumentNullException(parameterName,
                    string.Format(argumentNullExceptionMessage, parameterName, ReflectionHelper.GetCallingMethod().Name,
                        ReflectionHelper.GetCallingMethod(1).Name));
        }

        public static T NotNull<T>(Expression<Func<T>> parameter)
        {
            var parameterToCheck = parameter.Compile()();
            if (parameterToCheck == null)
            {
                var parameterName = parameter.GetMemberName();
                throw new ArgumentNullException(parameterName,
                    string.Format(argumentNullExceptionMessage, parameterName, ReflectionHelper.GetCallingMethod().Name,
                        ReflectionHelper.GetCallingMethod(1).Name));
            }

            return parameterToCheck;
        }

        public static void NotNull(object parameter, string parameterName)
        {
            if (parameter == null)
                throw new ArgumentNullException(parameterName,
                    string.Format(argumentNullExceptionMessage, parameterName, ReflectionHelper.GetCallingMethod().Name,
                        ReflectionHelper.GetCallingMethod(1).Name));
        }

        public static void NotNull(Guid parameter, string parameterName)
        {
            if (parameter == Guid.Empty)
                throw new ArgumentNullException(parameterName,
                    string.Format(argumentNullExceptionMessage, parameterName, ReflectionHelper.GetCallingMethod().Name,
                        ReflectionHelper.GetCallingMethod(1).Name));
        }

        public static void NotNullOrEmpty(string parameter, string parameterName)
        {
            if (string.IsNullOrEmpty(parameter))
                throw new ArgumentNullException(string.Format(argumentNullExceptionMessage, parameterName,
                    ReflectionHelper.GetCallingMethod().Name, ReflectionHelper.GetCallingMethod(1).Name));
        }


        public static void IsVistaOrHigher()
        {
            if (!(Environment.OSVersion.Version.Major >= 6))
                throw new InvalidOperationException("Invalid OperatingSystem");
        }

        public static TResult TryCatch<TResult, TException>(Func<TResult> block,
            Func<TException, Exception>? onException = null)
            where TException : Exception
        {
            try
            {
                return block();
            }
            catch (TException e)
            {
                if (onException != null)
                    throw onException(e);
            }

            return default(TResult);
        }

        public static async Task TryCatchAsync<TResult, TException>(Func<TResult> block,
            Func<TException, Exception>? onException = null)
            where TException : Exception
        {
            await Task.Run(() => TryCatch(block, onException));
        }

        public static async Task TryCatchAsync<TException>(Action block, Func<TException, Exception> onException = null)
            where TException : Exception
        {
            await Task.Run(() => TryCatch(block, onException));
        }

        public static async Task<TResult> TryCatchAsync<TResult, TException>(Task<TResult> task,
            Func<TException, Exception>? onException = null, CancellationToken cancellation = default(CancellationToken))
            where TException : Exception
        {
            try
            {
                return await task;
            }
            catch (TException e)
            {
                if (onException != null)
                    throw onException(e);
                return default(TResult);
            }
        }

        public static async Task TryCatchAsync<TException>(Task block, Func<TException, Exception> onException = null)
            where TException : Exception
        {
            try
            {
                await block;
            }
            catch (TException e)
            {
                if (onException != null)
                    throw onException(e);
            }
        }

        public static void TryCatch<TException>(Action block, Func<TException, Exception>? onException = null)
            where TException : Exception
        {
            try
            {
                block();
            }
            catch (TException e)
            {
                if (onException != null)
                    throw onException(e);
            }
        }
    }
}